// Copyright 2022 AndeyaLee. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"fmt"
	"net"
	"strings"
)

// ConfigValidator provides configuration validation functionality
type ConfigValidator struct {
	config *Config
	errors []string
}

// NewConfigValidator creates a new configuration validator
func NewConfigValidator(config *Config) *ConfigValidator {
	return &ConfigValidator{
		config: config,
		errors: make([]string, 0),
	}
}

// Validate validates the entire configuration
func (v *ConfigValidator) Validate() error {
	v.errors = v.errors[:0] // Reset errors

	v.validateNetworkTypes()
	v.validateAddresses()
	v.validateTimeouts()
	v.validateMultipart()
	v.validateSession()
	v.validateAPIDoc()
	v.validateXSRF()
	v.validateRouter()

	if len(v.errors) > 0 {
		return NewErrorWithDetails(ErrorCodeInvalidConfig,
			"configuration validation failed",
			strings.Join(v.errors, "; "))
	}

	return nil
}

// validateNetworkTypes validates network type configuration
func (v *ConfigValidator) validateNetworkTypes() {
	validTypes := map[string]bool{
		NetTypeHTTP:            true,
		NetTypeHTTPS:           true,
		NetTypeUnixHTTP:        true,
		NetTypeUnixHTTPS:       true,
		NetTypeLetsEncrypt:     true,
		NetTypeUnixLetsEncrypt: true,
	}

	types := v.config.NetTypes
	for _, netType := range types {
		netType = strings.TrimSpace(netType)
		if netType != "" && !validTypes[netType] {
			v.errors = append(v.errors, fmt.Sprintf("invalid network type: %s", netType))
		}
	}
}

// validateAddresses validates address configuration
func (v *ConfigValidator) validateAddresses() {
	addresses := v.config.Addrs
	for _, addr := range addresses {
		addr = strings.TrimSpace(addr)
		if addr == "" {
			continue
		}

		// Check if it's a Unix socket path
		if strings.HasPrefix(addr, "/") {
			continue
		}

		// Validate TCP address
		if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
			v.errors = append(v.errors, fmt.Sprintf("invalid address: %s (%v)", addr, err))
		}
	}
}

// validateTimeouts validates timeout configuration
func (v *ConfigValidator) validateTimeouts() {
	if v.config.ReadTimeout < 0 {
		v.errors = append(v.errors, "read timeout cannot be negative")
	}

	if v.config.WriteTimeout < 0 {
		v.errors = append(v.errors, "write timeout cannot be negative")
	}

	if v.config.SlowResponseThreshold < 0 {
		v.errors = append(v.errors, "slow response threshold cannot be negative")
	}
}

// validateMultipart validates multipart configuration
func (v *ConfigValidator) validateMultipart() {
	if v.config.MultipartMaxMemoryMB < 0 {
		v.errors = append(v.errors, "multipart max memory cannot be negative")
	}

	if v.config.MultipartMaxMemoryMB > 1024 {
		v.errors = append(v.errors, "multipart max memory should not exceed 1GB")
	}
}

// validateSession validates session configuration
func (v *ConfigValidator) validateSession() {
	if !v.config.Session.Enable {
		return
	}

	validProviders := map[string]bool{
		SessionProviderMemory:    true,
		SessionProviderFile:      true,
		SessionProviderRedis:     true,
		SessionProviderMySQL:     true,
		SessionProviderPostgres:  true,
		SessionProviderCouchbase: true,
		SessionProviderLedis:     true,
		SessionProviderSSDB:      true,
		SessionProviderMemcache:  true,
	}

	if !validProviders[v.config.Session.Provider] {
		v.errors = append(v.errors, fmt.Sprintf("invalid session provider: %s", v.config.Session.Provider))
	}

	if v.config.Session.CookieLifeSecond < 0 {
		v.errors = append(v.errors, "session cookie life cannot be negative")
	}

	if v.config.Session.GcLifeSecond < 0 {
		v.errors = append(v.errors, "session GC life cannot be negative")
	}

	if v.config.Session.MaxLifeSecond < 0 {
		v.errors = append(v.errors, "session max life cannot be negative")
	}

	if v.config.Session.MaxLifeSecond > 0 && int64(v.config.Session.CookieLifeSecond) > v.config.Session.MaxLifeSecond {
		v.errors = append(v.errors, "session cookie life cannot exceed max life")
	}
}

// validateAPIDoc validates API documentation configuration
func (v *ConfigValidator) validateAPIDoc() {
	if !v.config.APIdoc.Enable {
		return
	}

	if v.config.APIdoc.Path == "" {
		v.errors = append(v.errors, "API doc path cannot be empty")
	}

	if !strings.HasPrefix(v.config.APIdoc.Path, "/") {
		v.errors = append(v.errors, "API doc path must start with /")
	}
}

// validateXSRF validates XSRF configuration
func (v *ConfigValidator) validateXSRF() {
	if !v.config.XSRF.Enable {
		return
	}

	if v.config.XSRF.Key == "" {
		v.errors = append(v.errors, "XSRF key cannot be empty")
	}

	if len(v.config.XSRF.Key) < 8 {
		v.errors = append(v.errors, "XSRF key should be at least 8 characters")
	}

	if v.config.XSRF.ExpireSecond < 0 {
		v.errors = append(v.errors, "XSRF expire time cannot be negative")
	}
}

// validateRouter validates router configuration
func (v *ConfigValidator) validateRouter() {
	// Router configuration is mostly boolean flags, so no specific validation needed
	// But we can add validation for specific cases if needed
}

// ValidateConfig validates a configuration and returns any errors
func ValidateConfig(config *Config) error {
	validator := NewConfigValidator(config)
	return validator.Validate()
}

// SetDefaults sets default values for configuration
func SetDefaults(config *Config) {
	if len(config.NetTypes) == 0 {
		config.NetTypes = []string{NetTypeHTTP}
	}

	if len(config.Addrs) == 0 {
		config.Addrs = []string{DefaultHost + ":" + DefaultPort}
	}

	if config.ReadTimeout == 0 {
		config.ReadTimeout = DefaultReadTimeout
	}

	if config.WriteTimeout == 0 {
		config.WriteTimeout = DefaultWriteTimeout
	}

	if config.MultipartMaxMemoryMB == 0 {
		config.MultipartMaxMemoryMB = DefaultMultipartMaxMemory
	}

	if config.SlowResponseThreshold == 0 {
		config.SlowResponseThreshold = 0
	}

	// Set session defaults
	if config.Session.Provider == "" {
		config.Session.Provider = DefaultSessionProvider
	}

	if config.Session.Name == "" {
		config.Session.Name = DefaultSessionName
	}

	if config.Session.GcLifeSecond == 0 {
		config.Session.GcLifeSecond = DefaultSessionGCLife
	}

	if config.Session.MaxLifeSecond == 0 {
		config.Session.MaxLifeSecond = DefaultSessionMaxLife
	}

	// Set API doc defaults
	if config.APIdoc.Path == "" {
		config.APIdoc.Path = DefaultAPIDocPath
	}

	// Set XSRF defaults
	if config.XSRF.Key == "" {
		config.XSRF.Key = DefaultXSRFKey
	}

	if config.XSRF.ExpireSecond == 0 {
		config.XSRF.ExpireSecond = DefaultXSRFExpire
	}
}

// SanitizeConfig sanitizes configuration values
func SanitizeConfig(config *Config) {
	// Trim whitespace from string values
	normalizedTypes := make([]string, 0, len(config.NetTypes))
	for _, t := range config.NetTypes {
		t = strings.TrimSpace(t)
		if t != "" {
			normalizedTypes = append(normalizedTypes, t)
		}
	}
	config.NetTypes = normalizedTypes

	normalizedAddresses := make([]string, 0, len(config.Addrs))
	for _, addr := range config.Addrs {
		addr = strings.TrimSpace(addr)
		if addr != "" {
			normalizedAddresses = append(normalizedAddresses, addr)
		}
	}
	config.Addrs = normalizedAddresses

	config.Session.Name = strings.TrimSpace(config.Session.Name)
	config.Session.Provider = strings.TrimSpace(config.Session.Provider)
	config.APIdoc.Path = strings.TrimSpace(config.APIdoc.Path)
	config.XSRF.Key = strings.TrimSpace(config.XSRF.Key)

	// Ensure paths start with /
	if config.APIdoc.Path != "" && !strings.HasPrefix(config.APIdoc.Path, "/") {
		config.APIdoc.Path = "/" + config.APIdoc.Path
	}
}

// ValidateAndSetDefaults validates configuration and sets defaults
func ValidateAndSetDefaults(config *Config) error {
	SetDefaults(config)
	SanitizeConfig(config)
	return ValidateConfig(config)
}
